# -*- coding: utf-8 -*-
# 模型、时间、定位信息处理
import sys

if 'threading' in sys.modules:
    del sys.modules['threading']
if '..' not in sys.path:
    sys.path.append('..')

from gevent import monkey

monkey.patch_all(Event=True, dns=False)
import math
import setting
import urllib
import urllib2
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import json
from functools import partial
import datetime
import decimal
import bson
from gevent import socket
from model import Maprecord, JobBox, Device, SOS, MessageCenter
from cache import redis
from packet import interact
from agent.alphabet import OK

import gevent
import logging
import os

if os.getcwd()[-3:] == 'web':
    logger = logging.getLogger('web')
else:
    logger = logging.getLogger('server')


def jsonfixs(obj):
    if isinstance(obj, datetime.datetime):
        if obj.year < 1900:
            return '00-00-00 00:00:00'
        return obj.strftime('%Y-%m-%d %H:%M:%S')
    if isinstance(obj, datetime.date):
        if obj.year < 1900:
            return '00-00-00'
        return obj.strftime('%Y-%m-%d')
    if isinstance(obj, datetime.time):
        return obj.strftime('%H:%M')
    if isinstance(obj, decimal.Decimal):
        return "%.2f" % (obj,)
    if isinstance(obj, bson.objectid.ObjectId):
        return str(obj)
    raise TypeError(repr(obj) + ' is not JSON serializable')


dumps = partial(json.dumps, default=jsonfixs)
json.dumps = dumps


def getDistance(x1, y1, x2, y2):
    """
    获取两地GPS间距离,两地距离不能相同
    params: x1,y1,x2,y2 <float>
    """
    if x1 == x2 and y1 == y2:
        return 0.0
    lon1 = x1 * math.pi / 180.0
    lat1 = y1 * math.pi / 180.0
    lon2 = x2 * math.pi / 180.0
    lat2 = y2 * math.pi / 180.0
    con = math.sin(lat1) * math.sin(lat2)
    con += math.cos(lat1) * math.cos(lat2) * math.cos(lon1 - lon2)
    return round(math.acos(con) * 6378137.0 / 1000, 9) * 1000


amapKey = setting.server['amapKey']
gpsRadius = setting.locate['GPS']
lbsRadius = setting.locate['LBS']


def LocateHandle(conn, params, lon='0', lat='0', active=True):
    """
    解析T29,T48基站定位指令
    param: conn    <DeviceConnect>
    param: params <str>
           Examples:
            "count$1$0|28725|18784"
            "mcount$2$460$0$255#2503|af65|50|32#2503|4364|77|23"
    param: lon    <str>
    param: lat    <str>
    """
    imei = conn.imei
    if lon != '0' and lat != '0':
        useGPS = True
    else:
        useGPS = False
    rawLon = float(lon)
    rawLat = float(lat)
    if useGPS:
        cname = 'GPS:%s,%s' % (lon, lat)
    else:
        cname = 'LBS:' + params
    cache = redis.get(cname)
    if not cache and useGPS:
        try:
            resolve = urllib2.urlopen(
                'http://restapi.amap.com/v3/assistant/coordinate/convert?locations=%s,%s&coordsys=gps&key=70e7644f3386ad7962853d8ebe6c084e' % (
                    lon, lat)).read()
            result = json.loads(resolve)
        except:
            logger.error('Assistant Amap Request Faild')
            return None
        if result['status'] == '1':
            Lon, Lat = result['locations'].split(',')
            lon = float(Lon)
            lat = float(Lat)
        else:
            logger.error('Assistant Amap Bad Result ' + resolve)
            return None
        try:
            resolve = urllib2.urlopen(
                'http://restapi.amap.com/v3/geocode/regeo?key=386cdf42d85e68f8e9a197d0b9f58a92&location=%s,%s' % (
                    Lon, Lat)).read()
            result = json.loads(resolve)
        except:
            logger.error('Geocode Amap Request Faild')
            return None
        if result['status'] == '1':
            try:
                address = result['regeocode']['formatted_address']
            except KeyError:
                address = ''
            addressComponent = result['regeocode']['addressComponent']
            try:
                province = addressComponent['province'] if addressComponent['province'] else ''
            except KeyError:
                province = ''
            try:
                city = addressComponent['city'] if addressComponent['city'] else ''
            except KeyError:
                city = ''
            try:
                road = result['regeocode']['streetNumber']['street']
            except KeyError:
                road = ''
            try:
                citycode = addressComponent['citycode'] if addressComponent['citycode'] else ''
            except KeyError:
                citycode = ''
            try:
                adcode = addressComponent['adcode']
            except KeyError:
                adcode = ''
            try:
                radius = int(result['result']['radius'])
            except KeyError:
                radius = 0
            poi = ''
            redis.setex(cname, 43200, json.dumps({
                'address': address,
                'province': province,
                'city': city,
                'road': road,
                'poi': poi,
                'citycode': citycode,
                'adcode': adcode,
                'lon': lon,
                'lat': lat,
                'radius': radius
            }))
        else:
            logger.error('Geocode Amap Bad Result ' + resolve)
            return None
    elif not cache:
        amap = {
            'key': amapKey,
            'accesstype': 0,
            'cdma': 0,
            'imei': imei,
            'imsi': conn.identify,
            'serverip': '10.0.0.172',
            'network': 'GSM'
        }
        if params.startswith('count'):
            # 单基站
            head, lac, cid = params.split('|')
            mnc = head.split('$')[-1]
            amap['bts'] = '460,' + mnc + ',' + lac + ',' + cid + ',-65'
        else:
            # 多基站
            _, _, mcc, mnc, _ = params.split('#')[0].split('$')
            mdata = params.split('#')[1:]
            nearbts = []
            for i, d in enumerate(mdata):
                lac, cid, rpow, rlex = d.split('|')
                # 小区编号,基站编号,信号频点,信号强度
                if lac == '0' or cid == '0':
                    return None
                rlex = int(rlex) - 110
                # rlex信号强度 - 110 后单位为 dBm,一般为 0~-133dBm
                if i == 0:
                    amap['bts'] = mcc + ',' + mnc + ',' + str(int(lac, 16)) + ',' + str(int(cid, 16)) + ',' + str(rlex)
                else:
                    nearbts.append(
                        mcc + ',' + mnc + ',' + str(int(lac, 16)) + ',' + str(int(cid, 16)) + ',' + str(rlex))
            amap['nearbts'] = '|'.join(nearbts)

        try:
            resolve = urllib2.urlopen('http://apilocate.amap.com/position?' + urllib.urlencode(amap)).read()
            result = json.loads(resolve)
        except:
            logger.error('Locate Amap Request Faild')
            return None

        if result['status'] == '1' and result['result']['type'] != '0':
            # 高德解析基站数据成功
            try:
                address = result['result']['desc'].replace(' ', '')
                # 位置描述
            except KeyError:
                address = ''
            try:
                province = result['result']['province']
                # 省
            except KeyError:
                province = ''
            try:
                city = result['result']['city']
                # 市
            except KeyError:
                city = ''
            try:
                road = result['result']['road']
                # 道路名称
            except KeyError:
                road = ''
            try:
                poi = result['result']['poi']
                # 附近poi
            except KeyError:
                poi = ''
            try:
                citycode = result['result']['citycode']
                # 城市编码
            except KeyError:
                citycode = ''
            try:
                adcode = result['result']['adcode']
                # 区域编码
            except KeyError:
                adcode = ''
            try:
                lon, lat = result['result']['location'].split(',')
                # 定位坐标
            except KeyError:
                lon = 0
                lat = 0
            try:
                radius = int(result['result']['radius'])
                # 定位精度,半径范围
            except KeyError:
                radius = 0
            lon = float(lon)
            lat = float(lat)
            radius = int(radius)
            redis.setex(cname, 43200, json.dumps({
                'address': address,
                'province': province,
                'city': city,
                'road': road,
                'poi': poi,
                'citycode': citycode,
                'adcode': adcode,
                'lon': lon,
                'lat': lat,
                'radius': radius
            }))
        else:
            # logger.error('Locate Amap Bad Result ' + resolve)
            return None
    else:
        # 本地高德定位缓存
        cache = json.loads(cache)
        address = cache['address']
        province = cache['province']
        city = cache['city']
        road = cache['road']
        poi = cache['poi']
        citycode = cache['citycode']
        adcode = cache['adcode']
        lon = float(cache['lon'])
        lat = float(cache['lat'])
        radius = int(cache['radius'])

    m = Maprecord(dev=imei, address=address, province=province, city=city, road=road, poi=poi, citycode=citycode,
                  adcode=adcode, lon=lon, lat=lat, radius=radius, useGPS=useGPS, rawLon=rawLon, rawLat=rawLat,
                  lbs=params)
    m.save()

    dev = conn.model
    family = dev.family

    # 上一条指令如果为sos指令
    if dev.sos:
        SOS(dev=imei, address=address, lon=lon, lat=lat, time=m.time).save()
        if family:
            FamilyPush(family, {
                'type': 'babysos',
                'imei': imei,
                'babyname': dev.name,
                'address': address,
                'time': m.time
            })
        dev.sos = False
        conn.new_sos = False

    if family and active:
        # 存在家庭圈且定位为手动定位
        FamilyPush(family, {
            'type': 'nowlocate',
            'imei': imei,
            'address': address.decode('utf8') if isinstance(address, str) else address,
            'name': dev.name,
            'lon': lon,
            'lat': lat,
            'time': m.time,
            'radius': gpsRadius if useGPS else lbsRadius,
        })

    enterSaferegion = {}
    # 宝贝进入了多少个安全区域
    lastsafe = None
    # 防止安全区域被删除后与lastRegionId不匹配
    for safe in dev.saferegion:
        distance = getDistance(lon, lat, safe.lon, safe.lat)
        if distance <= safe.radius:
            enterSaferegion[safe.id] = safe
        if safe.id == dev.lastRegionId:
            lastsafe = safe

    if dev.saferegion:
        if not dev.lastRegionId and enterSaferegion:
            # 进入安全区域
            safe = enterSaferegion.itervalues().next()
            MessageCenter(dev=imei, type='safe',
                          value=dumps({'name': safe.name, 'condition': 'enter', 'lon': lon, 'lat': lat})).save()
            FamilyPush(family, {
                'type': 'safemessage',
                'imei': imei,
                'address': address,
                'name': dev.name,
                'lon': lon,
                'lat': lat,
                'condition': 'enter',
                'hour': m.time.hour,
                'minute': m.time.minute
            })
            dev.lastRegionId = safe.id
            conn.new_lastRegionId = safe.id
        if dev.lastRegionId and not enterSaferegion and lastsafe:
            # 离开安全区域
            MessageCenter(dev=imei, type='safe',
                          value=dumps({'name': lastsafe.name, 'condition': 'leave', 'lon': lon, 'lat': lat})).save()
            FamilyPush(family, {
                'type': 'safemessage',
                'imei': imei,
                'address': address,
                'name': dev.name,
                'lon': lon,
                'lat': lat,
                'condition': 'leave',
                'hour': m.time.hour,
                'minute': m.time.minute
            })
            dev.lastRegionId = 0
            conn.new_lastRegionId = 0
        if dev.lastRegionId and enterSaferegion and dev.lastRegionId not in enterSaferegion:
            # 进入安全区域且离开之前的安全区域
            if lastsafe:
                MessageCenter(dev=imei, type='safe',
                              value=dumps({'name': lastsafe.name, 'condition': 'leave', 'lon': lon, 'lat': lat})).save()
            safe = enterSaferegion.itervalues().next()
            MessageCenter(dev=imei, type='safe',
                          value=dumps({'name': safe.name, 'condition': 'enter', 'lon': lon, 'lat': lat})).save()
            FamilyPush(family, {
                'type': 'safemessage',
                'imei': imei,
                'address': address,
                'name': dev.name,
                'lon': lon,
                'lat': lat,
                'condition': 'enter',
                'hour': m.time.hour,
                'minute': m.time.minute
            })
            dev.lastRegionId = safe.id
            conn.new_lastRegionId = safe.id


def addJobBox(imei, instruct, params):
    """
    添加指令到终端任务箱中
    param: imei     <str>
    param: instruct <str>
    param: params   <list>
    """
    Device.objects(imei=imei).update_one(push__jobbox=JobBox(instruct=instruct, params=','.join(params)))


timeago = {
    'china': {
        'second': u'秒',
        'minute': u'分钟',
        'hour': u'小时',
        'day': u'天',
        'month': u'个月',
        'year': u'年'
    },
    'english': {
        'second': u' seconds',
        'minute': u' minutes',
        'hour': u' hour',
        'day': u' days',
        'month': u' months',
        'year': u' years'
    }
}


def timeHandle(before, after, language):
    """
    显示两时间间距,为 now - date
    显示两时间间距,为 after - before
    param : before <datetime.datetime>
    param : after  <datetime.datetime>
    return: 时间间隔,间隔类型
    """
    difference = after - before
    if difference.days > 0:
        if difference.days > 365:
            return '%s%s' % (difference.days / 365, timeago[language]['year'])
        if difference.days > 30:
            return '%s%s' % (difference.days / 30, timeago[language]['month'])
        return '%s%s' % (difference.days, timeago[language]['day'])
    if difference.seconds > 3600:
        hour = difference.seconds / 3600
        return '%s%s%s%s' % (
            hour, timeago[language]['hour'], (difference.seconds - hour * 3600) / 60, timeago[language]['minute'])
    if difference.seconds > 60:
        return '%s%s' % (difference.seconds / 60, timeago[language]['minute'])
    return '%s%s' % (difference.seconds, timeago[language]['second'])


def conditionHandle(data, language):
    if language != 'english' and data == 'enter':
        return u'进入'
    if language != 'english' and data == 'leave':
        return u'离开'
    return data


def sendMail(recver, topic, content):
    smtp = smtplib.SMTP()
    smtp.connect(setting.email['server'], setting.email['port'])
    smtp.login(setting.email['username'], setting.email['password'])
    msg = MIMEMultipart()
    msg['From'] = setting.email['sender']
    msg['To'] = recver
    msg['Subject'] = topic
    txt = MIMEText(content)
    msg.attach(txt)
    smtp.sendmail(
        setting.email['sender'],
        recver,
        msg.as_string()
    )
    smtp.quit()


PUSH_host = setting.push['host']
PUSH_port = setting.push['port']


def FamilyPush(family, data):
    """
    给家庭成员推送消息
    :family  <list>  ['559385ad56a04024706879b0']
    :data    <dict>  推送消息,含'mtype'字段
    """
    if family:
        try:
            s = socket.socket()
            s.connect((PUSH_host, PUSH_port))
            s.send(dumps({'family': family, 'data': data}))
            s.close()
        except:
            logger.error('Failed Push Proxy Connect %r' % data, exc_info=True)


def SchedulerSilent_on(imei):
    for i in xrange(3):
        result = interact(imei, 'S41', 'on')
        if result == OK:
            Device.objects(imei=imei).update_one(silentoption=True)
            break
        else:
            gevent.sleep(i * 3 + 3)


def SchedulerSilent_off(imei):
    for i in xrange(3):
        result = interact(imei, 'S41', 'off')
        if result == OK:
            Device.objects(imei=imei).update_one(silentoption=False)
            break
        else:
            gevent.sleep(i * 3 + 3)
